
#pragma once

#ifndef ELL_VEC_H
#define ELL_VEC_H

#include <stdlib.h>

struct ell_vec_core {
    size_t capacity;
    size_t size;
};

#define ELL_VEC_RAW(name, type)   \
    struct name {                 \
        struct ell_vec_core core; \
        type;                     \
    }

#define ELL_VEC_DECLARE(name, type) ELL_VEC_RAW(name, type* data)

#define ELL_VEC_DECLARE_FP(name, ret, ...) \
    ELL_VEC_RAW(name, ret (**data)(__VA_ARGS__))

#define ELL_VEC_DECLARE_TDEF(name, type) \
    typedef ELL_VEC_DECLARE(name, type) name

#define ELL_VEC_RAW_TDEF(name, type) typedef ELL_VEC_RAW(name, type) name

/* Initialization and Modifying */

#define ELL_VEC_INIT(vec) ell__vec_init(&(vec)->core, (void**)&(vec)->data)

#define ELL_VEC_INIT_RESERVE(vec, i)                       \
    do {                                                   \
        ell__vec_init(&(vec)->core, (void**)&(vec)->data); \
        ELL_VEC_RESERVE((vec), (i));                       \
    } while (0)

#define ELL_VEC_INIT_SIZE(vec, i)                          \
    do {                                                   \
        ell__vec_init(&(vec)->core, (void**)&(vec)->data); \
        ELL_VEC_RESIZE((vec), (i));                        \
    } while (0)

#define ELL_VEC_INIT_FILL(vec, s, i)   \
    do {                               \
        ELL_VEC_INIT_SIZE((vec), (s)); \
        ELL_VEC_FILL((vec), (i));      \
    } while (0)

#define ELL_VEC_PUSH(vec, value)                                     \
    do {                                                             \
        ell__vec_ensure_capacity(&(vec)->core, (void**)&(vec)->data, \
                                 sizeof(*((vec)->data)));            \
        (vec)->data[(vec)->core.size++] = (value);                   \
    } while (0)

#define ELL_VEC_POP(vec) ((vec)->core.size--)

#define ELL_VEC_RESET(vec) ell__vec_reset(&(vec)->core, (void**)&(vec)->data)

#define ELL_VEC_RESERVE(vec, i)                          \
    ell__vec_reserve(&(vec)->core, (void**)&(vec)->data, \
                     sizeof(*((vec)->data)), (i));

#define ELL_VEC_RESIZE(vec, i)                          \
    ell__vec_resize(&(vec)->core, (void**)&(vec)->data, \
                    sizeof(*((vec)->data)), (i));

#define ELL_VEC_AT(vec, i) ((vec)->data[(i)])

#define ELL_VEC_FRONT(vec) ELL_VEC_AT((vec), 0)

#define ELL_VEC_BACK(vec) ELL_VEC_AT((vec), (vec)->core.size - 1)

#define ELL_VEC_PTR(vec, i) ((vec)->data + (i))

#define ELL_VEC_FILL(vec, i)                                         \
    do {                                                             \
        if ((vec)->core.size == 0) {                                 \
            break;                                                   \
        }                                                            \
        ((vec))->data[0] = (i);                                      \
        ell__vec_fill_from_first(&(vec)->core, (void*)((vec)->data), \
                                 sizeof(*((vec)->data)));            \
    } while (0)

/* Queries */

// XXX: build wrapper functions such that size and capacity cannot
//      be written through these macros.
#define ELL_VEC_SIZE(vec) ((vec)->core.size)
#define ELL_VEC_CAPACITY(vec) ((vec)->core.capacity)

/* Implementation */

void ell__vec_init(struct ell_vec_core* core, void** data);
void ell__vec_ensure_capacity(struct ell_vec_core* core, void** data,
                              size_t element_size);
void ell__vec_reset(struct ell_vec_core* core, void** data);
void ell__vec_reserve(struct ell_vec_core* core, void** data,
                      size_t element_size, size_t elements);
void ell__vec_resize(struct ell_vec_core* core, void** data,
                     size_t element_size, size_t elements);
void ell__vec_fill_from_first(struct ell_vec_core* core, void* data,
                              size_t element_size);

/* Convenience types */

ELL_VEC_DECLARE(ell_vec_uint, unsigned int);
ELL_VEC_DECLARE(ell_vec_int, int);
ELL_VEC_DECLARE(ell_vec_ulong, unsigned long);
ELL_VEC_DECLARE(ell_vec_long, long);
ELL_VEC_DECLARE(ell_vec_float, float);
ELL_VEC_DECLARE(ell_vec_double, double);

#endif
